package com.gitee.dubboOps;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.gitee.dubboOps.Exception.NotFoundException;
import com.gitee.dubboOps.Utils.GenericInvokeUtils;
import com.gitee.dubboOps.model.MetadataType;
import com.gitee.dubboOps.model.RedisInvokeParamModel;
import com.gitee.dubboOps.model.RedisServiceModel;
import com.gitee.dubboOps.model.ServiceDO;
import com.gitee.dubboOps.param.GenericInvokeMetadata;
import com.gitee.dubboOps.param.ServiceInvokeParam;
import com.gitee.dubboOps.param.ServiceInvokeResult;
import com.gitee.dubboOps.redis.Model.RedisDubboTestModel;
import com.gitee.dubboOps.redis.redisImpl.RedisDubboTestImpl;
import com.gitee.dubboOps.redis.redisImpl.RedisInvokeParamImpl;
import com.gitee.dubboOps.redis.redisImpl.RedisServiceImpl;
import com.google.gson.Gson;
import com.taobao.jaket.JaketTypeBuilder;
import com.taobao.jaket.model.MethodDefinition;
import com.taobao.jaket.model.ServiceDefinition;
import com.taobao.jaket.model.TypeDefinition;
import com.taobao.jaket.util.ClassUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;

@Service
public class ServiceServiceImpl implements ServiceService{
    @Autowired
    RedisDubboTestImpl redisDubboTest;

    @Autowired
    RedisInvokeParamImpl redisInvokeParam;

    @Autowired
    RedisServiceImpl redisServiceimpl;

    @Autowired
    DubboServiceFactory dubboServiceFactory;

    @Value("${dubbo.registry.address}")
    private String zkUrl;

    @Override
    public GenericInvokeMetadata getGenericInvokeMetadata(String envName, String serviceName, String methodSignature) throws Exception {
        String rediskey = envName+"#"+serviceName;
        if (redisDubboTest.isKeyExists(rediskey)) {
            RedisDubboTestModel redisDubboTestModel = redisDubboTest.get(rediskey);
            ServiceDefinition serviceDefinition = redisDubboTestModel.getServiceDefinition();
            GenericInvokeMetadata genericInvokeMetadata =
                    GenericInvokeUtils.getGenericInvokeMetadata(serviceDefinition, methodSignature, MetadataType.DEFAULT_VALUE);
            if(genericInvokeMetadata==null) {
                throw new Exception("Unserialize from redis error!");
            }
            return genericInvokeMetadata;
        } else {
            throw new Exception("can't found in redis,key:"+rediskey);
        }
    }

    @Override
    public String getInvokeMetaJson(String envName, String serviceName, String methodSignature) throws Exception {
        GenericInvokeMetadata genericInvokeMetadata = getGenericInvokeMetadata(envName, serviceName, methodSignature);
        return new Gson().toJson(genericInvokeMetadata.getParameterTypes());
    }


    @Override
    public  ServiceDefinition getServiceMetadata(String envName, String serviceName) throws Exception {
        String rediskey = envName+'#'+serviceName;
        if(redisDubboTest.isKeyExists(rediskey)) {
            RedisDubboTestModel redisDubboTestModel = redisDubboTest.get(rediskey);
            return redisDubboTestModel.getServiceDefinition();
        }

        try {
            Class clazz = Class.forName(serviceName);
            ServiceDefinition serviceDefinition = metadata(clazz);
            cache2Redis(envName, serviceName, serviceDefinition);
            return serviceDefinition;
        } catch (ClassNotFoundException e) {
            // 当前加载器没有加载成功，从nexus server拉取
            //1.先得到加载器
            ClassLoader cl = getClassLoader(envName, serviceName);

            //2.加载器中加载Class
            Class<?> c = cl.loadClass(serviceName);

            //3.解析各个方法，保存Redis
            ServiceDefinition serviceDefinition = metadata(c);

            cache2Redis(envName, serviceName, serviceDefinition);
            return serviceDefinition;
        } catch (Exception e) {
            throw new Exception(ExceptionUtils.getStackTrace(e));
        }
    }

    @Override
    public ServiceInvokeResult invoke(ServiceInvokeParam invokeParam) throws Exception {
        return dubboServiceFactory.invoke(invokeParam);
    }

    @Override
    public void storeTestData(String envName, String serviceName, String methodSignature, String invokeParam) throws Exception{
        try {
            String rediskey = envName+'#'+serviceName + "#" + methodSignature;
            RedisInvokeParamModel redisInvokeParamModel = new RedisInvokeParamModel();
            redisInvokeParamModel.setRediskey(rediskey);
            redisInvokeParamModel.setInvokeparam(invokeParam);
            redisInvokeParam.put(rediskey, redisInvokeParamModel, -1);
        } catch (Exception e) {
            throw new Exception("opt redis error!"+ExceptionUtils.getStackTrace(e));
        }
    }

    @Override
    public String loadTestData(String envName, String serviceName, String methodSignature) throws Exception{
        try {
            String rediskey = envName+'#'+serviceName + "#" + methodSignature;
            if (redisInvokeParam.isKeyExists(rediskey)) {
                return redisInvokeParam.get(rediskey).getInvokeparam();
            }
            return null;
        } catch (Exception e) {
            throw new Exception("opt redis error!"+ExceptionUtils.getStackTrace(e));
        }
    }

    @Override
    public  Collection<ServiceDO>  getServiceList() throws Exception {
        Map<String, ServiceDO> map = new HashMap<>();

        if(redisServiceimpl.isKeyExists(zkUrl)) {
            RedisServiceModel redisServiceModel = redisServiceimpl.get(zkUrl);
            map = redisServiceModel.getStringServiceDOMAP();
        }

        return map.values();
    }

    @Override
    public List<String> getMethodList(String env, String serviceName) throws Exception {
        ServiceDefinition sd = getServiceMetadata(env, serviceName);
        if (sd == null) {
            throw new NotFoundException("Metadata of service [" + serviceName + "] not found in environment [" + env + "]");
        }
        List<String> methodList = new ArrayList<String>();
        for (MethodDefinition md : sd.getMethods()) {
            methodList.add(GenericInvokeUtils.convertMethodDef2SignatureStr(md));
        }
        return null;
    }

    @Override
    public void removeCache(String env, String serviceName) throws Exception{
        try {
            String rediskey = env+'#'+serviceName;
            redisDubboTest.remove(rediskey);
        } catch (Exception e) {
            throw new Exception("opt redis error!"+ExceptionUtils.getStackTrace(e));
        }
    }


    private ClassLoader getClassLoader(String envName, String serviceName) throws Exception {
        String url = "http://nexus.zhenai.com/service/local/lucene/search?_dc=1544771225680&cn="+serviceName;
        String name = "Accept";
        String value = "application/json,application/vnd.siesta-error-v1+json,application/vnd.siesta-validation-errors-v1+json";
        HttpResponse httpResponse = HttpFetch(url, name, value);
        if(httpResponse==null) {
            throw new Exception("http error!");
        }

        String result = EntityUtils.toString(httpResponse.getEntity(),"utf-8");
        JSONArray jsonArray = JSON.parseObject(result).getJSONArray("data");
        if(jsonArray.isEmpty()) {
            throw new Exception("jsonArray is empty!!");
        }

        String v = ((JSONObject)jsonArray.get(0)).getString("latestSnapshot");
        String g = ((JSONObject)jsonArray.get(0)).getString("groupId");
        String a = ((JSONObject)jsonArray.get(0)).getString("artifactId");
        String r = ((JSONObject)jsonArray.get(0)).getString("latestSnapshotRepositoryId");
        if(r==null) {
            r = ((JSONObject)jsonArray.get(0)).getString("latestReleaseRepositoryId");
            v = ((JSONObject)jsonArray.get(0)).getString("latestRelease");
        }

        url = "http://nexus.zhenai.com/service/local/artifact/maven/resolve?_dc=1544776180483&r="+r+"&g="+g+"&a="+a+"&v="+v+"&c=&e=jar&isLocal=true";
        httpResponse = HttpFetch(url, name, value);

        result = EntityUtils.toString(httpResponse.getEntity(),"utf-8");
        JSONObject jsonObject = JSON.parseObject(result);
        String repo = jsonObject.getJSONObject("data").getString("repositoryPath");

        String jarPath = "http://nexus.zhenai.com/service/local/repositories/"+r+"/content" + repo;
        ClassLoader cl = new URLClassLoader(new URL[] { new URL(jarPath) });

        return cl;
    }


    private void cache2Redis(String envName, String serviceName, ServiceDefinition serviceDefinition) throws Exception{
        try {
            String rediskey = envName+'#'+serviceName;
            RedisDubboTestModel redisDubboTestModel = new RedisDubboTestModel();
            redisDubboTestModel.setEnv(envName);
            redisDubboTestModel.setRedisKey(rediskey);
            redisDubboTestModel.setServiceDefinition(serviceDefinition);
            redisDubboTest.put(rediskey, redisDubboTestModel,-1);
        } catch (Exception e) {
            throw new Exception("opt redis error!"+ExceptionUtils.getStackTrace(e));
        }
    }


    private  ServiceDefinition metadata(Class interfaceClass) {
        ServiceDefinition sd = new ServiceDefinition();
        sd.setCanonicalName(interfaceClass.getCanonicalName());
        sd.setCodeSource(ClassUtils.getCodeSource(interfaceClass));

        JaketTypeBuilder builder = new JaketTypeBuilder();
        List<Method> methods = ClassUtils.getPublicNonStaticMethods(interfaceClass);
        for (Method method : methods) {
            MethodDefinition md = new MethodDefinition();
            md.setName(method.getName());

            Class<?>[] paramTypes = method.getParameterTypes();
            Type[] genericParamTypes = method.getGenericParameterTypes();

            String[] parameterTypes = new String[paramTypes.length];
            for (int i = 0; i < paramTypes.length; i++) {
                try {
                    TypeDefinition td = builder.build(genericParamTypes[i], paramTypes[i]);
                    parameterTypes[i] = td.getType();
                } catch (Exception e) {
                    parameterTypes[i] = paramTypes[i].getName();
                }
            }
            md.setParameterTypes(parameterTypes);

            TypeDefinition td = builder.build(method.getGenericReturnType(), method.getReturnType());
            md.setReturnType(td.getType());

            sd.getMethods().add(md);
        }

        sd.setTypes(builder.getTypeDefinitions());
        return sd;
    }

    @Override
    public ServiceDefinition  getServiceDefinitonFromJar(List<String> jarPaths, String serviceName) throws Exception{
        List<URL> arrayList = new ArrayList<>();
        for(String path: jarPaths){
            arrayList.add(new URL(path));
        }

        URL[] urls = arrayList.toArray(new URL[jarPaths.size()]);
        ClassLoader cl = new URLClassLoader(urls);
        Class<?> c = cl.loadClass(serviceName);
        ServiceDefinition serviceDefinition = metadata(c);
        return serviceDefinition;
    }

    @Override
    public void parseJar(String env,List<String> jarPaths, String serviceName) throws Exception {
        ServiceDefinition serviceDefinition = getServiceDefinitonFromJar(jarPaths, serviceName);
        cache2Redis(env, serviceName, serviceDefinition);
    }

    private HttpResponse HttpFetch(String url, String name, String value) throws Exception{
        try {
            HttpGet httpGet = new HttpGet(url);
            DefaultHttpClient httpClient = new DefaultHttpClient();
            httpGet.setHeader(name, value);
            HttpResponse httpResponse = httpClient.execute(httpGet);
            if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                throw new Exception("HTTP get from nexus error!");
            }
            return httpResponse;
        } catch (Exception e) {
            throw new Exception(ExceptionUtils.getStackTrace(e));
        }
    }
}